﻿# ------------------------- #
# Virtual Network Functions #
# ------------------------- #
Import-Module "$env:dp0\Modules\Core\IC.Azure.Xml.psm1" -DisableNameChecking -Force

# Add virtual network objects
function Add-VirtualNetworks([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.dnsServers)) -and !(Should-Run($subscription.localNetworks)) -and !(Should-Run($subscription.virtualNetworks))) {return}
	[xml]$config = Get-VirtualNetworkConfiguration
	Add-DnsServers $subscription $config
	Add-LocalNetworks $subscription $config
	Add-VirtualNetworkSites $subscription $config
	Update-VirtualNetworkConfiguration $config
    Assign-VirtualNetworkSitesResourceGroup $subscription
	Add-VirtualNetworkGateways $subscription $config
}

# Remove virtual network objects
function Remove-VirtualNetworks([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.dnsServers)) -and !(Should-Run($subscription.localNetworks)) -and !(Should-Run($subscription.virtualNetworks))) {return}
	[xml]$config = Get-VirtualNetworkConfiguration
    Remove-VirtualNetworkGateways $subscription $config
	Remove-VirtualNetworkSites $subscription $config
	Remove-LocalNetworks $subscription $config
	Remove-DnsServers $subscription $config
	Update-VirtualNetworkConfiguration $config
}

# Return virtual network configuration
function Get-VirtualNetworkConfiguration
{
	[xml]$config = (Get-AzureVNetConfig).XMLConfiguration
	if ($config -eq $null)
	{
		$config = [xml]"<?xml version=""1.0"" encoding=""utf-8""?>
		<NetworkConfiguration xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration"">
			<VirtualNetworkConfiguration />
		</NetworkConfiguration>"
	}
	$script:configUpdated = $false
	return $config
}

# Update virtual network configuration
function Update-VirtualNetworkConfiguration([xml]$config)
{
	if ($script:configUpdated)
	{
		Write-Information "Updating network configuration ... " -NoNewLine $true
		$file = "$Env:TEMP\NetworkConfig.xml"
		$config.Save($file)
		$operation = Set-AzureVNetConfig -ConfigurationPath $file
		if (!$? -or $operation.OperationStatus -ne "Succeeded") {throw "-"}
		Remove-Item $file
		Write-Text "done."
	}
}

# Add DNS servers
function Add-DnsServers([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.dnsServers))) {return}
	foreach ($server in $subscription.dnsServers.dnsServer)
	{
	    if (!(Should-Run($server))) {continue}
		Write-Information "Adding DNS server '$($server.name)' ... " -NoNewLine $true
		if ($config.NetworkConfiguration.VirtualNetworkConfiguration.Dns.DnsServers.DnsServer | ? {$_.name -eq $server.name})
		{
			Write-Text "skipped."
		}
		else
		{
			$virtualNetworkConfiguration = Get-XmlNode $config $config.NetworkConfiguration $config.NetworkConfiguration.VirtualNetworkConfiguration "VirtualNetworkConfiguration"
			$dns = Get-XmlNode $config $virtualNetworkConfiguration $virtualNetworkConfiguration.Dns "Dns"
			$dnsServers = Get-XmlNode $config $dns $dns.DnsServers "DnsServers"
			$node = $config.CreateElement("DnsServer", $config.DocumentElement.NamespaceURI)
			$attribute = $config.CreateAttribute("name")
			$attribute.Value = $server.name
			$node.Attributes.Append($attribute) | Out-Null
			$attribute = $config.CreateAttribute("IPAddress")
			$attribute.Value = $server.ipAddress
			$node.Attributes.Append($attribute) | Out-Null
			$dnsServers.AppendChild($node) | Out-Null
			$script:configUpdated = $true
			Write-Text "done."
		}
	}
}

# Remove DNS servers
function Remove-DnsServers([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.dnsServers))) {return}
	foreach ($server in $subscription.dnsServers.dnsServer)
	{
	    if (!(Should-Run($server))) {continue}
		Write-Information "Removing DNS server '$($server.name)' ... " -NoNewLine $true
        $node = $config.NetworkConfiguration.VirtualNetworkConfiguration.Dns.DnsServers.DnsServer | ? {$_.name -eq $server.name}
		if ($node -ne $null)
		{
			$config.NetworkConfiguration.VirtualNetworkConfiguration.Dns.DnsServers.RemoveChild($node) | Out-Null
			$script:configUpdated = $true
			Write-Text "done."
		}
		else
		{
			Write-Text "skipped."
		}
	}
}

# Add local networks
function Add-LocalNetworks([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.localNetworks))) {return}
	foreach ($network in $subscription.localNetworks.localNetwork)
	{
	    if (!(Should-Run($network))) {continue}
		Write-Information "Adding local network '$($network.name)' ... " -NoNewLine $true
		$node = $config.NetworkConfiguration.VirtualNetworkConfiguration.LocalNetworkSites.LocalNetworkSite | ? {$_.name -eq $network.name}
		if ($node -ne $null)
		{
            $ipAddress = $network.vpnGatewayAddress
            if ($network.vpnGatewayNetwork -ne $null)
            {
                $ipAddress = (Get-AzureVNetGateway -VNetName $network.vpnGatewayNetwork -ErrorAction SilentlyContinue).VIPAddress
            }
            if ((![string]::IsNullOrEmpty($ipAddress)) -and $ipAddress -ne $node.VPNGatewayAddress)
            {
				$node.VPNGatewayAddress = $ipAddress
			    $script:configUpdated = $true
			    Write-Text "done."
            }
            else
            {
			    Write-Text "skipped."
            }
		}
		else
		{
			$virtualNetworkConfiguration = Get-XmlNode $config $config.NetworkConfiguration $config.NetworkConfiguration.VirtualNetworkConfiguration "VirtualNetworkConfiguration"
			$localNetworkSites = Get-XmlNode $config $virtualNetworkConfiguration $virtualNetworkConfiguration.LocalNetworkSites "LocalNetworkSites" $virtualNetworkConfiguration.Dns
			$node = $config.CreateElement("LocalNetworkSite", $config.DocumentElement.NamespaceURI)
			$attribute = $config.CreateAttribute("name")
			$attribute.Value = $network.name
			$node.Attributes.Append($attribute) | Out-Null
			$node = $localNetworkSites.AppendChild($node)
			if ($network.vpnGatewayAddress -ne $null)
			{
				$vpnNode = $node.OwnerDocument.CreateElement("VPNGatewayAddress", $node.OwnerDocument.DocumentElement.NamespaceURI)
				$vpnNode.InnerText = $network.vpnGatewayAddress
				$node.AppendChild($vpnNode) | Out-Null
			}
			$script:configUpdated = $true
			Write-Text "done."
		}
		Add-AddressSpaces "local network '$($network.name)'" (Get-XmlNode $config $node $node.AddressSpace "AddressSpace") $network.addressSpaces.addressSpace
	}
}

# Remove local networks
function Remove-LocalNetworks([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.localNetworks))) {return}
	foreach ($network in $subscription.localNetworks.localNetwork)
	{
	    if (!(Should-Run($network))) {continue}
		Write-Information "Removing local network '$($network.name)' ... " -NoNewLine $true
		$node = $config.NetworkConfiguration.VirtualNetworkConfiguration.LocalNetworkSites.LocalNetworkSite | ? {$_.name -eq $network.name}
		if ($node -ne $null)
		{
			$config.NetworkConfiguration.VirtualNetworkConfiguration.LocalNetworkSites.RemoveChild($node) | Out-Null
			$script:configUpdated = $true
			Write-Text "done."
		}
		else
		{
			Write-Text "skipped."
		}
	}
}

# Add virtual network sites
function Add-VirtualNetworkSites([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.virtualNetworks))) {return}
	foreach ($network in $subscription.virtualNetworks.virtualNetwork)
	{
	    if (!(Should-Run($network))) {continue}
	    Write-Information "Adding virtual network '$($network.name)' ... " -NoNewLine $true
        $node = $config.NetworkConfiguration.VirtualNetworkConfiguration.VirtualNetworkSites.VirtualNetworkSite | ? {$_.name -eq $network.name}
	    if ($node -ne $null)
	    {
		    Write-Text "skipped."
	    }
	    else
	    {
		    $virtualNetworkConfiguration = Get-XmlNode $config $config.NetworkConfiguration $config.NetworkConfiguration.VirtualNetworkConfiguration "VirtualNetworkConfiguration"
		    $virtualNetworkSites = Get-XmlNode $config $virtualNetworkConfiguration $virtualNetworkConfiguration.VirtualNetworkSites "VirtualNetworkSites"
		    $node = $config.CreateElement("VirtualNetworkSite", $config.DocumentElement.NamespaceURI)
		    $attribute = $config.CreateAttribute("name")
		    $attribute.Value = $network.name
		    $node.Attributes.Append($attribute) | Out-Null
		    $attribute = $config.CreateAttribute("Location")
		    $attribute.Value = $network.location
		    $node.Attributes.Append($attribute) | Out-Null
		    $node = $virtualNetworkSites.AppendChild($node)
		    $script:configUpdated = $true
		    Write-Text "done."
	    }
	    Add-AddressSpaces "virtual network '$($network.name)'" (Get-XmlNode $config $node $node.AddressSpace "AddressSpace") $network.addressSpaces.addressSpace
	    Add-VirtualNetworkSubnets "virtual network '$($network.name)'" (Get-XmlNode $config $node $node.Subnets "Subnets") $network.subnets.subnet
	    Add-VirtualNetworkDnsServers "virtual network '$($network.name)'" (Get-XmlNode $config $node $node.DnsServersRef "DnsServersRef") $network.dnsServers.dnsServer
        $gateway = Get-XmlNode $config $node $node.Gateway "Gateway"
        Add-VirtualNetworkPointToSiteConnectivity "virtual network '$($network.name)'" $gateway $network.gateway.pointToSite
		Add-VirtualNetworkSiteToSiteConnectivity "virtual network '$($network.name)'" $gateway $network.gateway.siteToSite
    }
}

# Remove virtual network sites
function Remove-VirtualNetworkSites([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.virtualNetworks))) {return}
	foreach ($network in $subscription.virtualNetworks.virtualNetwork)
	{
	    if (!(Should-Run($network))) {continue}
	    Write-Information "Removing virtual network '$($network.name)' ... " -NoNewLine $true
	    $node = $config.NetworkConfiguration.VirtualNetworkConfiguration.VirtualNetworkSites.VirtualNetworkSite | ? {$_.name -eq $network.name}
	    if ($node -ne $null)
	    {
		    $config.NetworkConfiguration.VirtualNetworkConfiguration.VirtualNetworkSites.RemoveChild($node) | Out-Null
		    $script:configUpdated = $true
		    Write-Text "done."
	    }
	    else
	    {
		    Write-Text "skipped."
	    }
    }
}

# Assign virtual network sites resource group
function Assign-VirtualNetworkSitesResourceGroup([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.virtualNetworks))) { return }
	foreach ($network in $subscription.virtualNetworks.virtualNetwork)
	{
	    if (!(Should-Run($network))) { continue }
        if ($network.resourceGroup -ne $null)
        {
	        Write-Information "Adding virtual network '$($network.name)' resource group '$($network.resourceGroup)' ... " -NoNewLine $true
	        if (Move-ResourceItem $network.name "Microsoft.ClassicNetwork/virtualNetworks" $network.resourceGroup)
	        {
		        Write-Text "done."
	        }
	        else
	        {
		        Write-Text "skipped."
	        }
        }
    }
}

# Add virtual network gateways
function Add-VirtualNetworkGateways([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.virtualNetworks))) {return}
	foreach ($network in $subscription.virtualNetworks.virtualNetwork)
	{
	    if (!(Should-Run($network)) -or $network.gateway -eq $null) {continue}
	    Write-Information "Adding virtual network '$($network.name)' gateway ... " -NoNewLine $true
        $gateway = Get-AzureVNetGateway -VNetName $network.name
	    if ($gateway -ne $null -and $gateway.State -ne "NotProvisioned")
	    {
		    Write-Text "skipped."
	    }
	    else
	    {
            # type="DynamicRouting|StaticRouting"
            # sku="Default|HighPerformance"
		    $operation = New-AzureVNetGateway -VNetName $network.name -GatewayType $network.gateway.type -GatewaySKU $network.gateway.sku
	        if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual network '$($network.name)' gateway operation failed." }
		    Write-Text "done."
	    }
        foreach ($localNetwork in $network.gateway.siteToSite.localNetwork)
        {
            if ($localNetwork.sharedKey -ne $null)
            {
	            Write-Information "Adding virtual network '$($network.name)' gateway '$($localNetwork.name)' shared key ... " -NoNewLine $true
                if ((Get-AzureVNetGatewayKey -VNetName $network.name -LocalNetworkSiteName $localNetwork.name).Value -eq $localNetwork.sharedKey)
                {
                    Write-Text "skipped."
                }
                else
                {
                    Set-AzureVNetGatewayKey -VNetName $network.name -LocalNetworkSiteName $localNetwork.name -SharedKey $localNetwork.sharedKey | Out-Null
		            Write-Text "done."
                }
            }
        }
    }
}

# Remove virtual network gateways
function Remove-VirtualNetworkGateways([System.Xml.XmlElement]$subscription, [xml]$config)
{
	if (!(Should-Run($subscription.virtualNetworks))) {return}
	foreach ($network in $subscription.virtualNetworks.virtualNetwork)
	{
	    if (!(Should-Run($network)) -or $network.gateway -eq $null) {continue}
	    Write-Information "Removing virtual network '$($network.name)' gateway ... " -NoNewLine $true
        $gateway = Get-AzureVNetGateway -VNetName $network.name -ErrorAction SilentlyContinue
	    if ($gateway -ne $null -and $gateway.State -ne "NotProvisioned")
	    {
		    $operation = Remove-AzureVNetGateway -VNetName $network.name
	        if (!$? -or $operation.Status -ne "Successful") { throw "Virtual network '$($network.name)' gateway operation failed." }
		    Write-Text "done."
	    }
	    else
	    {
		    Write-Text "skipped."
	    }
    }
}

# Add address spaces
function Add-AddressSpaces($message, $addressSpace, $addressSpaces)
{
	foreach ($space in $addressSpaces)
	{
		Write-Information "Adding $message address space '$($space.addressPrefix)' ... " -NoNewLine $true
		if ($addressSpace.ChildNodes | ? {$_.InnerText -eq $space.addressPrefix})
		{
			Write-Text "skipped."
		}
		else
		{
			$node = $addressSpace.OwnerDocument.CreateElement("AddressPrefix", $addressSpace.OwnerDocument.DocumentElement.NamespaceURI)
			$node.InnerText = $space.addressPrefix
			$addressSpace.AppendChild($node) | Out-Null
			$script:configUpdated = $true
			Write-Text "done."
		}
	}
}

# Add virtual network subnets
function Add-VirtualNetworkSubnets($message, $configSubnets, $subnets)
{
	foreach ($net in $subnets)
	{
		Write-Information "Adding $message subnet '$($net.name)' ... " -NoNewLine $true
		if ($configSubnets.Subnet | ? {$_.name -eq $net.name})
		{
			Write-Text "skipped."
		}
		else
		{
			$node = $configSubnets.OwnerDocument.CreateElement("Subnet", $configSubnets.OwnerDocument.DocumentElement.NamespaceURI)
			$attribute = $config.CreateAttribute("name")
			$attribute.Value = $net.name
			$node.Attributes.Append($attribute) | Out-Null
			$address = $configSubnets.OwnerDocument.CreateElement("AddressPrefix", $configSubnets.OwnerDocument.DocumentElement.NamespaceURI)
			$address.InnerText = $net.addressPrefix
			$node.AppendChild($address) | Out-Null
			$configSubnets.AppendChild($node) | Out-Null
			$script:configUpdated = $true
			Write-Text "done."
		}
	}
}

# Add virtual network DNS servers
function Add-VirtualNetworkDnsServers($message, $configDnsServersRef, $dnsServers)
{
	foreach ($dnsServer in $dnsServers)
	{
		Write-Information "Adding $message DNS server '$($dnsServer.name)' ... " -NoNewLine $true
		if ($configDnsServersRef.DnsServerRef | ? {$_.name -eq $dnsServer.name})
		{
			Write-Text "skipped."
		}
		else
		{
			$node = $configDnsServersRef.OwnerDocument.CreateElement("DnsServerRef", $configDnsServersRef.OwnerDocument.DocumentElement.NamespaceURI)
			$attribute = $config.CreateAttribute("name")
			$attribute.Value = $dnsServer.name
			$node.Attributes.Append($attribute) | Out-Null
			$configDnsServersRef.AppendChild($node) | Out-Null
			$script:configUpdated = $true
			Write-Text "done."
		}
	}
}

# Add virtual network Point-To-Site connectivity
function Add-VirtualNetworkPointToSiteConnectivity($message, $gateway, $connectivity)
{
	if ($connectivity -ne $null)
	{
		Write-Information "Adding $message Point-To-Site connectivity ... " -NoNewLine $true
		if ($gateway.VPNClientAddressPool.AddressPrefix -eq $connectivity.clientAddressSpace)
		{
			Write-Text "skipped."
		}
		else
		{
            Remove-XmlNodeChild $gateway "VPNClientAddressPool"
		    $vpnClientAddressPool = $gateway.OwnerDocument.CreateElement("VPNClientAddressPool", $gateway.OwnerDocument.DocumentElement.NamespaceURI)
		    $localNetworkSiteRef = $vpnClientAddressPool.OwnerDocument.CreateElement("AddressPrefix", $vpnClientAddressPool.OwnerDocument.DocumentElement.NamespaceURI)
            $localNetworkSiteRef.InnerText = $connectivity.clientAddressSpace
		    $vpnClientAddressPool.AppendChild($localNetworkSiteRef) | Out-Null
            if ($gateway.ChildNodes.Count -eq 0)
            {
		        $gateway.AppendChild($vpnClientAddressPool) | Out-Null
            }
            else
            {
                $gateway.InsertBefore($vpnClientAddressPool, $gateway.ChildNodes[0]) | Out-Null
            }
		    $script:configUpdated = $true
		    Write-Text "done."
        }
	}
}

# Add virtual network Site-To-Site gateway
function Add-VirtualNetworkSiteToSiteConnectivity($message, $gateway, $connectivity)
{
    if ($connectivity.localNetwork -ne $null)
    {
		Write-Information "Adding $message Site-To-Site connectivity ... " -NoNewLine $true
        $rebuild = $false
        foreach ($localNetwork in $connectivity.localNetwork)
        {
            if (($gateway.ConnectionsToLocalNetwork.LocalNetworkSiteRef | ? {$_.name -eq $localNetwork.name }) -eq $null)
            {
                $rebuild = $true
                break
            }
        }
        if (!$rebuild)
		{
			Write-Text "skipped."
		}
        else
        {
            Remove-XmlNodeChild $gateway "ConnectionsToLocalNetwork"
            $connectionsToLocalNetwork = $gateway.OwnerDocument.CreateElement("ConnectionsToLocalNetwork", $gateway.OwnerDocument.DocumentElement.NamespaceURI)
            foreach ($localNetwork in $connectivity.localNetwork)
            {
		        $localNetworkSiteRef = $connectionsToLocalNetwork.OwnerDocument.CreateElement("LocalNetworkSiteRef", $connectionsToLocalNetwork.OwnerDocument.DocumentElement.NamespaceURI)
		        $attribute = $config.CreateAttribute("name")
		        $attribute.Value = $localNetwork.name
		        $localNetworkSiteRef.Attributes.Append($attribute) | Out-Null
		        $connection = $localNetworkSiteRef.OwnerDocument.CreateElement("Connection", $localNetworkSiteRef.OwnerDocument.DocumentElement.NamespaceURI)
		        $attribute = $config.CreateAttribute("type")
                # connectionType="IPsec|Dedicated"
		        $attribute.Value = $localNetwork.connectionType
		        $connection.Attributes.Append($attribute) | Out-Null
		        $localNetworkSiteRef.AppendChild($connection) | Out-Null
                $connectionsToLocalNetwork.AppendChild($localNetworkSiteRef) | Out-Null
		        $script:configUpdated = $true
            }
            $gateway.AppendChild($connectionsToLocalNetwork) | Out-Null
		    Write-Text "done."
        }
    }
}

# Return virtual network location
function Get-VirtualNetworkLocation($subscription, $virtualNetworkname)
{
    $result = $subscription.virtualNetworks.virtualNetwork | ? {$_.name -eq $virtualNetworkname}
    if ($result -eq $null) { throw "Virtual network '$virtualNetworkname' not found." }
    return $result.location
}